﻿using MySqlX.XDevAPI.Common;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace Moon.Sql
{
    public class MySqlInsertable<T> : InsertableProvide<T>
    {
        internal bool IsIgnoreInto = false;
        internal bool IsReplaceInto = false;
        internal bool IsOnDuplicateKeyUpdate = false;
        protected int batchSize = 5000;
        protected int batchColumnSize = 3000;

        public MySqlInsertable(IDatabase _Database)
         : base(_Database)
        {
        }

        public string GetOnDuplicateKeyUpdateSql(string sql)
        {
            if (!IsOnDuplicateKeyUpdate) return sql;
            sql += "\r\non duplicate key update\r\n";
            var columns = MasterTable.Properties.Where(i => !i.IsPrimaryKey && !i.IsIdentity && !i.Ignored);
            if (!columns.Any())
            {
                throw new ArgumentException("no column");
            }
            //过滤忽略的列
            if (!string.IsNullOrWhiteSpace(_IgnoreColumns))
            {
                var ignoreColumns = _IgnoreColumns.Split(',').Distinct().ToList();
                columns = columns.Where(i => !ignoreColumns.Contains(i.Name));
            }
            foreach (var item in columns)
            {
                sql += $"`{item.ColumnName}` = values(`{item.ColumnName}`),\r\n";
            }
            return sql.TrimEnd(",\r\n".ToArray());
        }

        public override int Execute()
        {
            SqlBuilderResult result = ToSql();
            if (IsIgnoreInto)
            {
                result.Sql = $"insert ignore into {result.Sql.Substring(12)}";
            }
            if (IsReplaceInto)
            {
                result.Sql = $"replace into {result.Sql.Substring(12)}";
            }
            result.Sql = GetOnDuplicateKeyUpdateSql(result.Sql);
            return Database.Execute(result.Sql, result.DynamicParameters);
        }

        public override async Task<int> ExecuteAsync()
        {
            SqlBuilderResult result = ToSql();
            if (IsIgnoreInto)
            {
                result.Sql = $"insert ignore into {result.Sql.Substring(12)}";
            }
            if (IsReplaceInto)
            {
                result.Sql = $"replace into {result.Sql.Substring(12)}";
            }
            result.Sql = GetOnDuplicateKeyUpdateSql(result.Sql);
            return await Database.ExecuteAsync(result.Sql, result.DynamicParameters);
        }

        public override long ExecuteIdentity()
        {
            if (!MasterTable.Properties.Where(i => i.IsIdentity).Any())
            {
                throw new ArgumentException("no identity column");
            }
            SqlBuilderResult result = ToSql();
            if (IsIgnoreInto)
            {
                result.Sql = $"insert ignore into {result.Sql.Substring(12)}";
            }
            if (IsReplaceInto)
            {
                result.Sql = $"replace into {result.Sql.Substring(12)}";
            }
            result.Sql = GetOnDuplicateKeyUpdateSql(result.Sql);
            result.Sql = string.Concat(result.Sql, ";select last_insert_id()");
            return Database.ExecuteScalar<long>(result.Sql, result.DynamicParameters);
        }

        public override async Task<long> ExecuteIdentityAsync()
        {
            if (!MasterTable.Properties.Where(i => i.IsIdentity).Any())
            {
                throw new ArgumentException("no identity column");
            }
            SqlBuilderResult result = ToSql();
            if (IsIgnoreInto)
            {
                result.Sql = $"insert ignore into {result.Sql.Substring(12)}";
            }
            if (IsReplaceInto)
            {
                result.Sql = $"replace into {result.Sql.Substring(12)}";
            }
            result.Sql = GetOnDuplicateKeyUpdateSql(result.Sql);
            result.Sql = string.Concat(result.Sql, ";select last_insert_id()");
            return await Database.ExecuteScalarAsync<long>(result.Sql, result.DynamicParameters);
        }

        public override DataTable ToDataTable()
        {
            if (SaveList == null || SaveList.Count == 0)
            {
                throw new Exception("object is empty");
            }
            Dictionary<string, string> ignoreColumnDict = null;
            if (!string.IsNullOrWhiteSpace(_IgnoreColumns))
            {
                ignoreColumnDict = _IgnoreColumns.Split(',').Distinct().ToDictionary(i => i);
            }
            DataTable table = new DataTable(MasterTable.TableName);
            foreach (var item in MasterTable.Properties)
            {
                if (item.IsIdentity || item.Ignored)
                {
                    continue;
                }
                if (ignoreColumnDict != null)
                {
                    if (ignoreColumnDict.ContainsKey(item.Name))
                        continue;
                }
                var nullType = Nullable.GetUnderlyingType(item.PropertyInfo.PropertyType);
                table.Columns.Add(item.ColumnName, nullType == null ? item.PropertyInfo.PropertyType : nullType);
            }
            int colCount = table.Columns.Count;
            foreach (var item in SaveList)
            {
                Type type = item.GetType();
                var entityValues = new object[colCount];
                for (int i = 0; i < colCount; i++)
                {
                    var property = type.GetProperty(table.Columns[i].ColumnName);
                    var dynamicMethod = CommonUtils.EmitGetProperty(property, true);
                    //mysql批量添加是通过文件导入的方式 不支持表中类型是数字 值是null
                    object val = DBNull.Value;
                    if (dynamicMethod != null)
                    {
                        var getterMethod = dynamicMethod.CreateDelegate(typeof(Func<T, object>)) as Func<T, object>;
                        val = getterMethod(item);
                        if (val == null)
                        {
                            val = DBNull.Value;
                        }
                    }
                    entityValues[i] = val;
                }
                table.Rows.Add(entityValues);
            }
            return table;
        }

        public override int BulkInsert()
        {
            if (SaveList == null || SaveList.Count == 0)
            {
                return 0;
            }
            string cols = string.Join(",", GetIgnoredColumns());
            return (Database as MySqlDatabase).BulkInsert<T>(SaveList, MasterTableName, cols);
        }

        public override async Task<int> BulkInsertAsync()
        {
            if (SaveList == null || SaveList.Count == 0)
            {
                return 0;
            }
            string cols = string.Join(",", GetIgnoredColumns());
            return await (Database as MySqlDatabase).BulkInsertAsync<T>(SaveList, MasterTableName, cols);
        }

        public int BulkInsert(string csvFilePath)
        {
            string cols = string.Join(",", GetIgnoredColumns());
            return (Database as MySqlDatabase).BulkInsert(csvFilePath, MasterTableName, cols);
        }

        public async Task<int> BulkInsertAsync(string csvFilePath)
        {
            string cols = string.Join(",", GetIgnoredColumns());
            return await (Database as MySqlDatabase).BulkInsertAsync(csvFilePath, MasterTableName, cols);
        }

        public int BulkInsert(DataTable table)
        {
            return (Database as MySqlDatabase).BulkInsert(table, MasterTableName);
        }

        public async Task<int> BulkInsertAsync(DataTable table)
        {
            return await (Database as MySqlDatabase).BulkInsertAsync(table, MasterTableName);
        }

        private string GetIgnoredColumns()
        {
            string cols = "";
            if (_IgnoreColumns == null)
            {
                var ignoredCols = MasterTable.Properties.Where(i => !i.Ignored && !i.IsIdentity).Select(i => i.Name);
                cols = string.Join(",", ignoredCols);
            }
            else
            {
                var sql = _IgnoreColumns;
                var ignoredCols = sql.Split(',').Distinct().ToList();
                cols = string.Join(",", MasterTable.Properties.Where(i => !i.Ignored && !i.IsIdentity && !ignoredCols.Contains(i.Name)).Select(i => i.Name));
            }
            return cols;
        }
    }
}
